﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Text;

namespace MUnit
{
    public class Core
    {
        public static string[] Separators = new string[] { Environment.NewLine };

        public static string CreateErrorMessage(string message)
        {
            StringBuilder builder = new StringBuilder();
            StackTrace trace = new StackTrace(true);
            StackFrame frame = trace.GetFrame(3);

            builder.Append(frame.GetFileName());
            builder.Append("(");
            builder.Append(frame.GetFileLineNumber());
            builder.Append("): error MUNIT001: ");
            builder.Append(message);

            return builder.ToString();
        }

        public static string CreateErrorMessageFromStackTrace(string stackTrace)
        {
            string[] strings = stackTrace.Split(Core.Separators, StringSplitOptions.RemoveEmptyEntries);
            StringBuilder builder = new StringBuilder(strings[strings.GetLength(0) - 1]);
            builder.Remove(0, builder.ToString().IndexOf(" in "));
            builder.Remove(0, " in ".Length);
            builder.Replace(":line ", "(");
            builder.Append("): error MUNIT003: Exception");

            StringBuilder stackTraceWithoutNewlines = new StringBuilder(stackTrace);
            stackTraceWithoutNewlines.Replace(Environment.NewLine, " ");
            builder.Append(stackTraceWithoutNewlines.ToString());

            return builder.ToString();
        }

        public static void TestAssembly(Assembly assembly)
        {
            int testCases = 0;
            int failures = 0;

            foreach (Type type in assembly.GetTypes())
            {
                List<TestMethod> methods = new List<TestMethod>();
                MethodInfo setupMethod = null;
                MethodInfo tearDownMethod = null;
                foreach (MethodInfo info in type.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public))
                {
                    bool testMethod = false;
                    ExpectedExceptionAttribute expectedAttribute = null;
                    foreach (object obj in info.GetCustomAttributes(false))
                    {
                        if (obj is TestAttribute) 
                        {
                            testMethod = true;
                        }
                        if (obj is ExpectedExceptionAttribute)
                        {
                            expectedAttribute = obj as ExpectedExceptionAttribute;
                        }
                        if (obj is SetupAttribute)
                        {
                            setupMethod = info;
                        }
                        if (obj is TearDownAttribute)
                        {
                            tearDownMethod = info;
                        }
                    }

                    if (testMethod)
                    {
                        methods.Add(new TestMethod(info, expectedAttribute));
                    }
                }

                foreach (TestMethod method in methods)
                {
                    System.Console.Write(type.Name + "." + method.MethodInfo.Name);
                    try
                    {
                        testCases++;
                        object instance = assembly.CreateInstance(type.FullName);
                        if (setupMethod != null)
                        {
                            setupMethod.Invoke(instance, null);
                        }
                        method.MethodInfo.Invoke(instance, null);
                        if (tearDownMethod != null)
                        {
                            tearDownMethod.Invoke(instance, null);
                        }

                        if (method.ExpectedException == null)
                        {
                            System.Console.WriteLine(" Success");
                        }
                        else
                        {
                            // TODO: Fix linking on this type of errors
                            // Current solution is simply dependant on that the type is located in a file with the same name.
                            System.Console.WriteLine(" Failure!!!");
                            System.Console.WriteLine(type.Name + ".cs: error MUNIT002: Expected exception not triggered");
                        }
                    }
                    catch (TargetInvocationException tie)
                    {
                        if (method.ExpectedException != null)
                        {
                            if (tie.InnerException.GetType().Equals(method.ExpectedException.Type))
                            {
                                System.Console.WriteLine(" Success");
                                continue;
                            }
                        }

                        failures++;
                        if (tie.InnerException is FailureException)
                        {
                            System.Console.WriteLine(" Failure!!!");
                            System.Console.WriteLine(tie.InnerException.Message);
                        }
                        else
                        {
                            System.Console.WriteLine(" Exception!!!");
                            System.Console.WriteLine(Core.CreateErrorMessageFromStackTrace(tie.InnerException.StackTrace));
                        }
                    }
                }
            }

            System.Console.WriteLine("Testcases: " + testCases.ToString() + ", failed: " + failures.ToString());
        }
    }
}
